package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"sync"
)

type Store interface {
	Put(url, key *string) error
	Get(url, key *string) error
}

type UrlStore struct {
	urls map[string]string
	mu   sync.RWMutex
	save chan record
}

type record struct {
	KEY, URL string
}

const saveQueueLength = 1000

func NewUrlStore(fileName string) *UrlStore {
	u := &UrlStore{urls: make(map[string]string)}
	if fileName != "" {
		u.save = make(chan record, saveQueueLength)
		if err := u.load(fileName); err != nil {
			log.Println("Error loading data in URLStore:", err)
		}
		go u.saveLoop(fileName)
	}

	return u
}

func (u *UrlStore) Get(key *string, url *string) error {
	u.mu.RLock()
	defer u.mu.RUnlock()
	if u, ok := u.urls[*key]; ok {
		*url = u
		return nil
	}
	return errors.New("key not found")
}

func (u *UrlStore) Set(key *string, url *string) error {
	u.mu.Lock()
	defer u.mu.Unlock()
	if _, existed := u.urls[*key]; existed {
		return errors.New("url already existed")
	}

	u.urls[*key] = *url
	return nil
}

func (u *UrlStore) count() int {
	u.mu.RLock()
	defer u.mu.RUnlock()
	return len(u.urls)
}

func (u *UrlStore) Put(url *string, key *string) error {
	for {
		*key = genKey(u.count())
		if err := u.Set(key, url); err != nil {
			break
		}
	}
	if u.save != nil {
		u.save <- record{*key, *url}
	}

	return nil
}

func (u *UrlStore) saveLoop(fileName string) error {
	f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalf("NewUrlStore Err:%s", err)
	}
	defer f.Close()
	e := json.NewEncoder(f)
	for {
		r := <-u.save
		if err := e.Encode(r); err != nil {
			log.Println("URLStore:", err)
		}
	}

}

func (u *UrlStore) load(fileName string) error {
	f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
	if err != nil {
		log.Fatalf("NewUrlStore Err:%s", err)
	}
	defer f.Close()

	if _, err := f.Seek(0, 0); err != nil {
		fmt.Println("failed to seek err")
		return err
	}

	d := json.NewDecoder(f)
	for err == nil {
		var r record
		if err = d.Decode(&r); err == nil {
			u.Set(&r.KEY, &r.URL)
		}
	}

	if err == io.EOF {
		return nil
	}

	return err
}
